#pragma once

#include "Core.h"
#include "Misc.h"

enum RevisionState
{
    RS_Uninit,
    RS_LoadedFromDisk,
    RS_CreatedInSession
};

class FileRevision
{
public:
    wstring fn;
    RevisionState st;
    int r;

    FileRevision() : fn(), r(-1), st(RS_Uninit) {}
    FileRevision(const wstring &fileName, int rev, RevisionState state) :
        fn(fileName), r(rev), st(state) {}
};

class File
{
    FILETIME created;
    map<int, FileRevision*> revs;
    int nextRev;
public:
    File(FILETIME createdFileTime) : created(createdFileTime), nextRev(0) {}
    ~File()
    {
        for (map<int, FileRevision*>::iterator i = revs.begin(); i != revs.end(); ++i) {
            delete (*i).second;
        }
    }

    void Open(HWND hwndRichEdit);
    void Save(HWND hwndRichEdit);

    void AddExistingRevision(LPCWSTR fileName, int rev)
    {
        revs[rev] = new FileRevision(fileName, rev, RS_LoadedFromDisk);
        nextRev = max(nextRev, rev + 1);
    }

    FileRevision *AddNewRevision()
    {
        FStr s(MAX_PATH);
        SYSTEMTIME t;
        FileTimeToSystemTime(&created, &t);
        StringCchPrintf(&s, s.size(), L"%.4d_%.2d_%.2d_%.2d_%.2d_%.2d_%.3d.rtf",
            t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond, nextRev);
        FileRevision *rev = new FileRevision(s.str(), nextRev, RS_CreatedInSession);
        revs[nextRev] = rev;
        nextRev++;
        return rev;
    }

    FILETIME Created() const { return created; }

private:
    static DWORD CALLBACK OpenStreamCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
    {
        HANDLE hFile = (HANDLE)dwCookie;
        DWORD n;
        DWORD res = !ReadFile(hFile, pbBuff, cb, &n, NULL);
        *pcb = n;
        return res;
    }

    static DWORD CALLBACK SaveStreamCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
    {
        HANDLE hFile = (HANDLE)dwCookie;
        DWORD n;
        DWORD res = !WriteFile(hFile, pbBuff, cb, &n, NULL);
        *pcb = n;
        return res;
    }
};

class RevisionManager
{
    vector<File*> files;
public:
    RevisionManager()
    {
        CreateDirectory(DataDir().c_str(), NULL);
    }
    ~RevisionManager() 
    {
        for (size_t i = 0; i < files.size(); i++) {
            delete (files[i]);
        }
    }

    void LoadRevisionsFromDir()
    {
        wstring search = DataDir() + L"\\*.rtf";
        
        WIN32_FIND_DATA fd = { 0 };
        HANDLE h = FindFirstFile(search.c_str(), &fd);
        BOOL cont = TRUE;
        while (h != INVALID_HANDLE_VALUE && cont) {
            RememberFile(fd.cFileName);
            cont = FindNextFile(h, &fd);
        }
        FindClose(h);

        sort(files.begin(), files.end(), AscendingRev);
    }

    void CreateNewFile()
    {
        FILETIME t;
        GetSystemTimeAsFileTime(&t);
        File *f = new File(t);
        files.push_back(f);
    }

    size_t NumFiles() const { return files.size(); }

    File &FileFromIndex(size_t i) { return *files.at(i); }

    static wstring DataDir()
    {
        FStr dir(MAX_PATH);
        GetEnvironmentVariable(L"APPDATA", &dir, DWORD(dir.size()));
        return dir.str() + L"\\Quickwrite";
    }

private:
    static bool AscendingRev(const File *a, const File *b) {
        return a->Created() < b->Created();
    }

    void RememberFile(LPCWSTR fileName)
    {
        int rev;
        SYSTEMTIME t = {0};
        int c = swscanf_s(fileName, L"%d_%d_%d_%d_%d_%d_%d",
            &t.wYear, &t.wMonth, &t.wDay, &t.wHour, &t.wMinute, &t.wSecond, &rev);
        if (c == 7) {
            FILETIME tt;
            SystemTimeToFileTime(&t, &tt);
            FileForTime(tt).AddExistingRevision(fileName, rev);
        }
    }

    File &FileForTime(const FILETIME &t) {
        for (size_t i = 0; i < files.size(); i++) {
            if (files[i]->Created() == t) {
                return *files[i];
            }
        }
        File *f = new File(t);
        files.push_back(f);
        return *f;
    }
};

void File::Open(HWND hwndRichEdit)
{
    //
    // Find the latest revision
    //
    FileRevision *r = NULL;
    for (map<int, FileRevision*>::iterator i = revs.begin(); i != revs.end(); ++i) {
        if (r == NULL || (*i).second->r > r->r) {
            r = (*i).second;
        }
    }
    if (r == NULL) {
        SetWindowText(hwndRichEdit, L"");
    }
    else {
        EDITSTREAM es = { 0 };
        const wstring path = RevisionManager::DataDir() + L"\\" + r->fn;
        HANDLE hFile = CreateFile(path.c_str(),
            GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        es.dwCookie = DWORD_PTR(hFile);
        es.pfnCallback = OpenStreamCallback;
        SendMessage(hwndRichEdit, EM_STREAMIN, SF_RTF, LPARAM(&es));
        CloseHandle(hFile);
    }
}


void File::Save(HWND hwndRichEdit)
{
    //
    // Find a revision created in this session
    //
    FileRevision *r = NULL;
    for (map<int, FileRevision*>::iterator i = revs.begin(); i != revs.end(); ++i) {
        if ((*i).second->st == RS_CreatedInSession) {
            r = (*i).second;
        }
    }
    if (r == NULL) {
        r = AddNewRevision();
    }

    //
    // Use the rich edit control to write the file
    //
    EDITSTREAM es = { 0 };
    const wstring path = RevisionManager::DataDir() + L"\\" + r->fn;
    HANDLE hFile = CreateFile(path.c_str(),
        GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    es.dwCookie = DWORD_PTR(hFile);
    es.pfnCallback = SaveStreamCallback;
    SendMessage(hwndRichEdit, EM_STREAMOUT, SF_RTF, LPARAM(&es));
    CloseHandle(hFile);
}


// 2007_08_18_01_10_00_000.rtf
// Y_M_D_H_m_s_ms_rev.rtf